package com.fvff.sanctuary.dao.impl;

import com.fvff.sanctuary.dao.GenericDao;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Order;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

/**
 * GenericDaoImpl
 * <p/>
 * This class is designed for extension.
 */
@Repository
public abstract class GenericDaoImpl<T> implements GenericDao<T> {
    private final Class<T> persistentClass;
    private SessionFactory sessionFactory;

    public final SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public final void setSessionFactory(final SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public final Session getSession() {
        return getSessionFactory().getCurrentSession();
    }

    /**
     * Constructor that takes in a class to see which type of entity to persist
     *
     * @param persistentClass the class type you'd like to persist
     */
    public GenericDaoImpl(final Class<T> persistentClass) {
        this.persistentClass = persistentClass;
    }

    @SuppressWarnings("unchecked")
    public final List<T> getAll() {
        Criteria criteria = getSession().createCriteria(this.persistentClass);
        // Remove duplicate objects.
        return new ArrayList(new HashSet(criteria.list()));
    }

    @SuppressWarnings("unchecked")
    public final List<T> paginateAll(final int index,
                                     final int maxRows,
                                     final String orderBy,
                                     final boolean ascending) {
        Criteria criteria = getSession().createCriteria(this.persistentClass);
        if (index > 0) {
            criteria.setFirstResult(index);
        }
        if (maxRows > 0) {
            criteria.setMaxResults(maxRows);
        }
        if (ascending) {
            criteria.addOrder(Order.asc(orderBy));
        } else {
            criteria.addOrder(Order.desc(orderBy));
        }
        return criteria.list();
    }

    @SuppressWarnings("unchecked")
    public final T get(final Long id) {
        return (T) getSession().load(this.persistentClass, id);
    }

    @SuppressWarnings("unchecked")
    public final T getIfExists(final Long id) {
        return (T) getSession().get(this.persistentClass, id);
    }

    @SuppressWarnings("unchecked")
    public final boolean exists(final Long id) {
        return getIfExists(id) != null;
    }

    @SuppressWarnings("unchecked")
    public final T save(final T object) {
        return (T) getSession().merge(object);
    }

    @SuppressWarnings("unchecked")
    public final Long create(final T object) {
        return (Long) getSession().save(object);
    }

    public final void flush() {
        getSession().flush();
    }

    public final void flushClear() {
        getSession().flush();
        getSession().clear();
    }

    /**
     * Each DAO must provide a remove method,
     * because a generic remove cannot handle all
     * possible cascade delete logic.
     *
     * @param id Long of entity to remove
     */
    public abstract void remove(final Long id);
}